const vscode = require('vscode');
const { kernel32, user32, getMessage, findWindow, createProcessA, findChildWindow, findProcessesByFilename, findPortsByProcessId } = require('../functions/window_api');
const { sleep_ms } = require('../functions/helper');
const fs = require('fs');
const { exec, execFile } = require('child_process');
const EventEmitter = require('events');
const path = require('path');
const { Configs } = require('../Configs');


/**
 * @extends {EventEmitter<{
 *  launch:[app:Application],
 *  launched:[app:Application],
 *  exit:[app:Application],
 *  destroy:[app:Application],
 *  error:[app:Application,error:Error],
 *  message:[logs:{ message:string, lines:string[] },app:Application],
 *  changeStatus:[app:Application,status:number, tip:string]
 * }>}
*/
class Application extends EventEmitter {
    /** @type {number} 未启动 */
    static STATUS_IDLE = 0;
    /** @type {number} 正在启动 */
    static STATUS_STARTING = 1;
    /** @type {number} 已启动 */
    static STATUS_LAUNCHED = 2;
    /** @type {number} 正在退出 */
    static STATUS_STOPING = 3;
    /** @type {number} 等待执行 */
    static STATUS_WAITING = 4;
    name = '';
    title = '';
    filename = "";
    hash = "";
    port = 0
    disabled = false
    /** @type {string|boolean} */
    workingDirectory = true
    launchDelay = 100
    /** @type {import('child_process').ChildProcess?} */
    childProcess = null
    /** @type {number} */
    pid = undefined
    /** @type {string|null} */
    mainClassName = null
    /** @type {string|null} */
    mainText = null
    /** @type {WindowHandle|null} */
    mainHandle = null;
    /** @type {string|null} */
    msgClassName = 'TMemo|TRichEdit|TRichView|TJvRichEdit|TRzMemo';
    /** @type {WindowHandle|null} */
    msgHandle = null;
    status = Application.STATUS_IDLE
    constructor(filename, name, workingDirectory, port) {
        super();
        this.filename = filename
        this.name = name
        this.port = port
        this.workingDirectory = workingDirectory
        this.init()
    }

    /**
     * 
     * @param {number} status 
     * @param {string} tooltip 
     */
    updateStatus(status, tooltip = null) {
        switch (status) {
            case Application.STATUS_IDLE:
                this.statusTooltip = tooltip || '未启动';
                break;
            case Application.STATUS_STARTING:
                this.statusTooltip = tooltip || '正在启动';
                break;
            case Application.STATUS_LAUNCHED:
                this.statusTooltip = tooltip || '已启动';
                break;
            case Application.STATUS_STOPING:
                this.statusTooltip = tooltip || '正在关闭';
                break;
            case Application.STATUS_WAITING:
                this.statusTooltip = tooltip || '等待任务执行...';
                break;
            default:
                break;
        }
        this.status = status
        this.emit("changeStatus", this, status, this.statusTooltip)
    }
    launchBefore(callback) {
        this._launchBefore = callback;
    }

    init() {
        const list = findProcessesByFilename(this.filename)
        const handle = list.find(v => {
            try {
                if (this.port) {
                    const ports = findPortsByProcessId(v.ProcessId)
                    return ports.includes(this.port)
                }
                return true
            } catch (error) {
                // console.log(error);
            }
        })
        if (handle) {
            this.pid = handle.ProcessId
            this.findMainWindow()
            this.updateStatus(Application.STATUS_LAUNCHED)
        }
    }

    async launch() {
        if (this.disabled) {
            return
        }
        this.emit("launch", this)
        if (typeof this._launchBefore === 'function') {
            let result = this._launchBefore();
            if (result instanceof Promise) {
                await result
            }
        }

        if (this.pid) {
            return this.pid
        }
        if (fs.existsSync(this.filename) === false) {
            vscode.window.showErrorMessage(`${this.title || this.name || this.filename}启动失败，文件不存在!`);
            return
        }
        this.updateStatus(Application.STATUS_STARTING)

        let workingDirectory = this.workingDirectory
        if (typeof workingDirectory !== 'string' && workingDirectory !== false) {
            workingDirectory = path.dirname(this.filename);
        }

        this.pid = await createProcessA(this.filename, { wShowWindow: 1, delay: 200 })
        await this.task(this.findMainWindow, 100, 5000)
        // 等一秒
        // await sleep_ms(1000)
        this.updateStatus(Application.STATUS_LAUNCHED)
        this.emit("launched", this)
        return this.childProcess
    }


    /**
     * @template T
     * @param {(index:number)=>Promise<T>|T} callback 
     * @param {number} delay 
     * @param {number} timeout 
     * @param {number} times 
     * @returns {Promise<T|null>}
     */
    task(callback, delay = 100, timeout = 1000, times = 0) {
        return new Promise(async (resolve) => {
            let isTimeout = false
            const timer = setTimeout(() => {
                isTimeout = true;
                console.log("超时");
                resolve(null)
            }, timeout)
            const total = times > 0 ? times : 2000
            let result = null
            let i = 0
            while (isTimeout === false && !result && i++ < total) {
                // console.log(`第${i}次尝试获取主窗口...`);
                await sleep_ms(delay)
                result = callback.call(this, i)
                if (result instanceof Promise) {
                    result = await result
                }
                if (result) {
                    break
                }
            }
            clearTimeout(timer)
            resolve(result)
        });
    }
    async getMessage() {
        if (!this.msgHandle) {
            await this.task(this.findMsgWindow, 100, 10000, 100)
            if (!this.msgHandle) {
                vscode.window.showErrorMessage(`${this.title || this.name}找不到消息窗口！`);
                clearTimeout(this.msgTimer)
            }
        }
        const message = getMessage(this.msgHandle.hwnd)
        const lines = message.split("\n").filter(v => !!v.trim())
        return { message, lines }
    }
    async exit() {
        if (!this.pid) {
            return true
        }
        this.emit("exit", this)
        this.updateStatus(Application.STATUS_STOPING)
        const pid = this.pid
        if (this.mainHandle?.hwnd) {
            // console.log("发送关闭消息", this.name, this.mainHandle.hwnd);
            await sleep_ms(500)
            // 发送关闭消息
            user32.PostMessageA(this.mainHandle.hwnd, 0x0010, 0, 0);
            const confirmWindow = await this.task(() => {
                return findWindow((info) => info.processId == pid && info.className == "#32770" && /.*[信息|提示].*/.test(info.text))
            }, 100, 5000)
            if (confirmWindow) {
                // console.log("确认关闭窗口", confirmWindow);
                user32.SendMessageA(confirmWindow.hwnd, 0x0111, 6, Buffer.alloc(64));
            } else {
                console.log("没有找到确认窗口", this.name, this.mainHandle);
            }

            // 等待进程退出
            const processHandle = kernel32.OpenProcess(0x00100000, false, pid);
            if (processHandle) {
                const waitResult = kernel32.WaitForSingleObject(processHandle, 5000); // 等待最多5秒
                kernel32.CloseHandle(processHandle)
                if (waitResult === 0) {
                    // console.log(`进程 ${pid} 已退出`);
                    this.destroy()
                    return true;
                }
                console.log(`等待进程 ${pid} 退出超时,${waitResult}`);
            } else {
                console.log(`无法打开进程 ${pid}`);
            }
        }
        // 直接杀掉进程
        exec(`taskkill /F /PID ${pid} /T`, (error, stdout, stderr) => {
            if (error) {
                console.error(`强制终止失败: ${error.message}`); // 错误处理:ml-citation{ref="2" data="citationList"}
                return;
            }
            console.log(`进程 ${pid} 及子进程已终止`);
            this.destroy()
        });

        return true
    }
    destroy() {
        clearInterval(this.msgTimer);
        this.updateStatus(Application.STATUS_IDLE)
        this.mainHandle = null;
        this.msgHandle = null;
        this.pid = undefined
        this.emit("destroy", this);
    }

    async reboot() {
        await this.exit()
        await sleep_ms(1000)
        return this.launch();
    }

    async findMainWindow() {
        if (!this.pid) {
            return null
        }
        const pid = this.pid;
        let classRegExp = null;
        let textRegExp = null;
        if (this.mainClassName !== null || this.mainText !== null) {
            classRegExp = new RegExp(this.mainClassName)
            textRegExp = new RegExp(this.mainText)
        }
        this.mainHandle = await findWindow((handle) => {
            if (handle.processId != pid || !handle.text) {
                return false
            }
            if (classRegExp?.test(handle.className) || textRegExp?.test(handle.text)) {
                // console.log("找到主窗口", handle);
                return true
            }
            const child = user32.GetWindow(handle.hwnd, 5)
            if (!child) {
                return false
            }
            // 返回true取第一个窗口 
            return true
        })
        return this.mainHandle
    }

    async findMsgWindow() {
        if (!this.pid || !this.mainHandle || !this.msgClassName) {
            return null
        }
        if (!this.msgHandle) {
            const pid = this.pid;
            let classRegExp = new RegExp(this.msgClassName);
            this.msgHandle = await findChildWindow(this.mainHandle.hwnd, (handle) => handle.processId == pid && classRegExp.test(handle.className))
        }
        return this.msgHandle
    }

    /**
     * 订阅消息
     */
    subscribe() {
        if (this.msgTimer) {
            clearInterval(this.msgTimer)
            this.msgTimer = undefined
        }
        let message = ""
        this.msgTimer = setInterval(async () => {
            const logs = await this.getMessage()
            if (!logs || logs.message === message) {
                return
            }
            message = logs.message
            this.emit("message", logs)
        }, 1000)
    }
}

/**
 * @typedef {{ file:ServerFile; app?: Application; hash:string; }} ServerTypeBase
 * @typedef {ServerTypeBase & {index: number; item:ServerFileItem; }} ServerChildrenType
 * @typedef {ServerTypeBase & {children?:ServerChildrenType[]; }} ServerType
 */

/**
 * @extends {EventEmitter<{
*  changeStatus:[app:Application; status:number; tip:string;]
* }>}
*/
class Servers extends EventEmitter {
    /** @type {(ServerType | ServerChildrenType)[]} */
    servers = []
    /** @type {ServerType[]} */
    files = []
    constructor() {
        super()
        this.init()
    }

    init() {
        const configs = new Configs()
        if (!configs.serverFiles) {
            return
        }
        this.files = configs.serverFiles.map(file => {
            const server = { file, index: -1, hash: file.hash }
            if (file.items && file.items.length > 0) {
                server.children = file.items.map((item, index) => {
                    const app = new Application(file.filename, file.name, true, item.port)
                    app.on("changeStatus", (...args) => {
                        this.emit("changeStatus", ...args)
                    })
                    const server = { file, app, index, item, hash: item.hash }
                    const beforeFunc = `before${file.name}`
                    if (typeof this[beforeFunc] === "function") {
                        app.launchBefore(() => this[beforeFunc](server, index))
                    }
                    app.hash = item.hash
                    this.servers.push(server)
                    return server
                });
            } else {
                const app = new Application(file.filename, file.name)
                app.on("changeStatus", (...args) => {
                    this.emit("changeStatus", ...args)
                })
                app.hash = file.hash
                server.app = app
                this.servers.push(server)
            }
            return server
        });
    }

    /**
     * 
     * @param {ServerType} server 
     * @param {number} index 
     */
    beforeRunGate(server, index) {
        const number = index + 1
        const configs = new Configs()
        configs.loadRootConfig();
        const workingDirectory = path.dirname(server.app.filename);
        const configFilename = path.join(workingDirectory, 'Config.ini');
        const runGateConfig = configs.loadIni(configFilename)
        runGateConfig.GameGate.GatePort = configs.rootConfig.RunGate[`GatePort${number}`]
        runGateConfig.GameGate.DBPort = configs.rootConfig.RunGate[`DBPort${number}`]
        runGateConfig.GameGate.Title = runGateConfig.GameGate.Title.replace(/\(([^)]+)\)$/, `(${runGateConfig.GameGate.GatePort})`)
        configs.saveIni(configFilename, runGateConfig)
    }
    async launch() {
        for (const key in this.servers) {
            const server = this.servers[key]
            await server.app.launch()
        }
    }
    async exit() {
        for (const key in this.servers) {
            const server = this.servers[key]
            await server.app.exit()
        }
    }
}


module.exports = { Application, Servers };